﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;

using SGCG.Helpers;

namespace SGCG.Model
{
    public class Line : BorderedGraphicObject, IHasArrow
    {

        public Line(GraphicObject parent) : base(parent) { }

        private void Swap(ref int a, ref int b)
        {
            a ^= b;
            b ^= a;
            a ^= b;
        }

        public override void Draw()
        {
            //UserMessage.Debug("dline");
            Recompute();
            int X0 = this.ComputedX1, Y0 = this.ComputedY1, X1 = this.ComputedX2, Y1 = this.ComputedY2;

            DrawArrow();

            bool steep = Math.Abs(Y1 - Y0) > Math.Abs(X1 - X0);

            if (steep)
            {
                Swap(ref X0, ref Y0);
                Swap(ref X1, ref Y1);
            }
            if (X0 > X1)
            {
                Swap(ref X0, ref X1);
                Swap(ref Y0, ref Y1);
            }

            int deltax = X1 - X0, deltay = Math.Abs(Y1 - Y0);
            int error = deltax / 2;
            int ystep = (Y0 < Y1) ? 1 : -1;
            int y = Y0;

            for (int x = X0; x <= X1; ++x)
            {
                if (ShouldPlot(x - X0, X1 - X0 + 1))
                {
                    if (steep)
                        SetPixel(y, x);
                    else
                        SetPixel(x, y);
                }

                error -= deltay;
                if (error < 0)
                {
                    y += ystep;
                    error += deltax;
                }
            }
        }

        private void DrawArrow()
        {
            if (ArrowType != ArrowType.Nenhum)
            {
                Point A = new Point(ComputedX1, ComputedY1), B = new Point(ComputedX2, ComputedY2);
                if (A.Subtract(B).Norm() < 1e-3)
                    return;
                Point dA = B.Subtract(A).Normalized();
                dA = dA.Multiply(10);
                ITransform rotate1 = new RotateTransform() { Angle = Math.PI / 12, RelativeTo = new Point(0, 0) };
                ITransform rotate2 = new RotateTransform() { Angle = 2 * Math.PI - Math.PI / 12, RelativeTo = new Point(0, 0) };
                Point A1 = A.Add(rotate1.Transform(dA));
                Point A2 = A.Add(rotate2.Transform(dA));
                Point dB = dA.Multiply(-1);
                Point B1 = B.Add(rotate1.Transform(dB));
                Point B2 = B.Add(rotate2.Transform(dB));
                if (ArrowType == ArrowType.PontaA || ArrowType == ArrowType.DuasPontas)
                    new Triangle(null)
                    {
                        X1 = (int)Math.Round(A.X),
                        X2 = (int)Math.Round(A1.X),
                        X3 = (int)Math.Round(A2.X),
                        Y1 = (int)Math.Round(A.Y),
                        Y2 = (int)Math.Round(A1.Y),
                        Y3 = (int)Math.Round(A2.Y),
                        LineStyle = Model.LineStyle.Normal,
                        Color = Color,
                        LineThickness = LineThickness,
                        Fill = FillType.Solid,
                        FillColor = Color
                    }.Attach(DrawingCanvas).Draw();
                if (ArrowType == ArrowType.PontaB || ArrowType == ArrowType.DuasPontas)
                    new Triangle(null)
                    {
                        X1 = (int)Math.Round(B.X),
                        X2 = (int)Math.Round(B1.X),
                        X3 = (int)Math.Round(B2.X),
                        Y1 = (int)Math.Round(B.Y),
                        Y2 = (int)Math.Round(B1.Y),
                        Y3 = (int)Math.Round(B2.Y),
                        LineStyle = Model.LineStyle.Normal,
                        Color = Color,
                        LineThickness = LineThickness,
                        Fill = FillType.Solid,
                        FillColor = Color
                    }.Attach(DrawingCanvas).Draw();
            }
        }

        private int x2;
        public int X2 
        {
            get { return this.x2; }
            
            set
            {
                if (x2 != value)
                {
                    x2 = value;
                    RaiseGraphicPropertyChanged("X2");
                }
            }
        }

        private int x1;
        public int X1 
        {
            get { return this.x1; }

            set
            {
                if (x1 != value)
                {
                    x1 = value;
                    RaiseGraphicPropertyChanged("X1");
                }
            }
        }

        private int y2;
        public int Y2 
        {
            get { return this.y2; }

            set
            {
                if (y2 != value)
                {
                    y2 = value;
                    RaiseGraphicPropertyChanged("Y2");
                }
            }
        }

        private int y1;
        public int Y1 
        { 
            get { return this.y1; }

            set
            {
                if (y1 != value)
                {
                    y1 = value;
                    RaiseGraphicPropertyChanged("Y1");
                }
            }
        }

        private ArrowType arrowType;
        public ArrowType ArrowType
        {
            get
            {
                return this.arrowType;
            }
            set
            {
                if (this.arrowType != value)
                {
                    this.arrowType = value;
                    RaiseGraphicPropertyChanged("ArrowType");
                }
            }
        }


        private bool ShouldPlot(int distanceFromStart, int totalLength)
        {
            if (this.LineStyle == Model.LineStyle.Normal)
            {
                return true;
            }
            else
            {
                int distanciaEntreTracejados = (LineStyle == LineStyle.Pontilhado) ? DotInterspace : DashInterspace;
                int tamanhoTracejado = (LineStyle == LineStyle.Pontilhado) ? 1 : DashSize;

                List<bool> toPaint = AdaptativeDashing.ComputeToPaint(totalLength, distanciaEntreTracejados, tamanhoTracejado);
                return toPaint[distanceFromStart];
            }
        }

        private void SetPixel(int x, int y)
        {
            this.DrawingCanvas.SetPixel(x, y, this.Color);

            if (this.LineThickness == Model.LineThickness.Dupla)
            {
                this.DrawingCanvas.SetPixel(x + 1, y + 1, this.Color);
                this.DrawingCanvas.SetPixel(x + 1, y, this.Color);
                this.DrawingCanvas.SetPixel(x, y + 1, this.Color);
            }
        }

        protected void CopyProperties(Line other)
        {
            base.CopyProperties(other);
            other.X1 = X1;
            other.Y1 = Y1;
            other.X2 = X2;
            other.Y2 = Y2;
            other.ArrowType = ArrowType;
        }

        public override GraphicObject CreateCopy()
        {
            Line line = new Line(null);
            CopyProperties(line);
            return line;
        }

        // These are only computed when Recompute() is called.
        public int ComputedX1 { get; private set; }
        public int ComputedY1 { get; private set; }
        public int ComputedX2 { get; private set; }
        public int ComputedY2 { get; private set; }

        /// <summary>
        /// Recomputes the coordinates of the object according to the transforms
        /// applied to it.
        /// </summary>
        public void Recompute()
        {
            Point p1 = TransformPoint(new Point(X1, Y1));
            Point p2 = TransformPoint(new Point(X2, Y2));
            ComputedX1 = (int)Math.Round(p1.X);
            ComputedY1 = (int)Math.Round(p1.Y);
            ComputedX2 = (int)Math.Round(p2.X);
            ComputedY2 = (int)Math.Round(p2.Y);
        }

        public override void ConsolidateTransform(ITransform transform)
        {
            Point p1 = transform.Transform(new Point(X1, Y1));
            Point p2 = transform.Transform(new Point(X2, Y2));
            X1 = (int)Math.Round(p1.X);
            Y1 = (int)Math.Round(p1.Y);
            X2 = (int)Math.Round(p2.X);
            Y2 = (int)Math.Round(p2.Y);
        }

    }
}
